Skip to content

Sync upstream v0.61.3 (merge conflicts)#7

Merged
trevorgowing merged 64 commits into
masterfrom
sync/upstream-2026-06-08
Jun 26, 2026
Merged

Sync upstream v0.61.3 (merge conflicts)#7
trevorgowing merged 64 commits into
masterfrom
sync/upstream-2026-06-08

Conversation

@trevorgowing

Copy link
Copy Markdown
Member

Upstream Sync — v0.61.3

Merge conflicts were detected when merging upstream release v0.61.3. Manual resolution is required before this PR can be merged into master.

Files with conflicts

frontend/src/metabase/ui/colors/constants/themes/dark.ts
frontend/src/metabase/ui/colors/constants/themes/light.ts

How to resolve

git fetch origin
git checkout sync/upstream-2026-06-08
# Resolve the <<<<<<< markers in each conflicted file, then:
git add -A
git commit --amend --no-edit
git push --force-with-lease origin sync/upstream-2026-06-08

Tip: The most likely files to conflict are the Teal branding files:
frontend/src/metabase/ui/colors/constants/themes/light.ts
frontend/src/metabase/ui/colors/constants/themes/dark.ts
src/metabase/appearance/settings.clj

github-automation-metabase and others added 30 commits May 19, 2026 14:11
…ts" (metabase#74408)

Skip API-key users when expanding notification recipients (metabase#73922)

Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
docs: usage analytics updates (metabase#74386)

* usage analytics updates

* Apply suggestions from code review



---------

Co-authored-by: Alex Yarosh <alexandra@metabase.com>
Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
…n" (metabase#74409)

Return client error when unable to acquire DB connection (metabase#74088)

* return client error for bad db details

* add test

* fix kondo

* fix test

* ignore kondo

* fix h2 and sqlite

* mock getConnection error

* fix kondo unused params

Co-authored-by: Riley Thompson <riley@metabase.com>
docs: ai updates (metabase#74388)

* ai updates

* Apply suggestions from code review



* links

---------

Co-authored-by: Alex Yarosh <alexandra@metabase.com>
Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
…etabase#74414)

Defensively support double-encoded search index rows (metabase#74402)

Co-authored-by: Chris Truter <crisptrutski@users.noreply.github.com>
* Update Security Center promo card layout (metabase#73918)

* wip

* Fix linter

* Remove old icon

* Fix channels refetching when Slack is setup

* Update SecurityCenterPromoCard tests for new copy

* GDGT-2345: Add SecurityCenterPromoCard banner to Admin's SettingsNav

* GDGT-2345: Fix unit tests for Admin notifications

* Add flag check for security center banner in SettingsNav

* GDGT-2345: Add new icon typing

---------

Co-authored-by: Stas Gavrylov <stas@metabase.com>
Co-authored-by: Stas Gavrylov <stasgavrylov@gmail.com>
…ase#74212)

* Add CSP nonce to SAML login popup for embedding (metabase#73861)

* Add CSP nonce to SAML login popup for embedding

* Add tests

---------

Co-authored-by: Mahatthana (Kelvin) Nomsawadi <me@bboykelvin.dev>

* Port necessary missing csp change

---------

Co-authored-by: Rodrigo López Dato <rodrigo@metabase.com>
Co-authored-by: Mahatthana (Kelvin) Nomsawadi <me@bboykelvin.dev>
docs: remove exp recommendation (metabase#73763)

Co-authored-by: Michael Eby <38187276+michaeleby1@users.noreply.github.com>
…#74430)

Metabot: read_resource navigation primitives (metabase#74238)

read_resource becomes an instance navigation primitive instead of a
dead-end leaf reader. URI dispatch runs through metabase.util.match
for a unified shape — adding a new pattern is one match clause plus
one focused fetch fn, replacing the sub-resource / path-segments slot
branching.

New URIs:
- Navigation: metabase://databases, metabase://collections (+ ?tree=true),
  metabase://user/recent-items
- Database drill-down: /database/{id}, /tables, /models, /schemas,
  /schemas/{name}/tables
- Collection drill-down: /collection/{id}, /items, /subcollections
- Lineage: /table/{id}/derived, /model/{id}/sources,
  /question/{id}/sources, /transform/{id}/sources, /transform/{id}/target
- Dashboard items: /dashboard/{id}/items

List responses cap at 25 with truncated/total signals so the agent
knows to drill into specific URIs rather than page blindly.

Each branch is permission-checked: single-entity URIs error when
mi/can-read? denies, list endpoints silently filter unreadable items.
Notably, /transform/{id}/target now gates the target table by
mi/can-read? — previously the target was hydrated from the transform
read without a per-table check.

URI segments are URL-encoded by the metabase-uri helper and decoded
by parse-uri, so schema names containing '/' (allowed by Postgres,
Snowflake, Redshift, SQL Server) round-trip correctly.

Co-authored-by: Mihael Konjević <konjevic@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ng between dashboards in InteractiveDashboard" (metabase#74434)

fix(sdk-bundle): host style not respected when navigating between dashboards in InteractiveDashboard (metabase#74345)

* refactor: drop dead fullscreen wiring from SDK dashboard wrapper

SDK fullscreen UI was removed in metabase#60198. The
`SdkDashboardStyledWrapperWithRef` variant existed solely to attach the
`fullscreenRef` from `DashboardContext` to the dashboard wrapper, but
nothing in the SDK paths triggers `onFullscreenChange` anymore, so the
ref was attached but never used.

Drop the WithRef variant and collapse `SdkDashboardStyledWrapper` to a
plain function component. Two call sites in `SdkDashboard.tsx` move to
the plain wrapper.

No behavior change — the inner `fullscreenRef` was dead-wired.



* fix: preserve user style when navigating between SDK dashboards

EMB-1746. When `<InteractiveDashboard enableEntityNavigation>` navigated
to another dashboard via custom click behavior, the navigated dashboard
lost the user-provided `style` / `className` (e.g. `height: 100dvh`),
breaking sticky filters and forcing full-width layout.

Root cause: `SdkInternalNavigationProvider` wraps navigated content in
an outer `SdkDashboardStyledWrapper` carrying user style, while
`SdkDashboard` always wraps its own render in another
`SdkDashboardStyledWrapper`. The provider explicitly stripped
`style`/`className` on the inner render to avoid double padding/borders
— but that also dropped the height, leaving the inner wrapper without
the scroll context the dashboard relies on for sticky pinning.

Fix: add `isInsideNavigationStack` to the navigation context. When set,
`SdkDashboard` skips its own styled wrapper, so the outer one (with
user style) stays the sole styled container. Drop the `style={undefined}`
and `className={undefined}` overrides from the provider — no longer
needed.

Verified with a new E2E test that asserts a single styled wrapper with
the user height after navigating to another dashboard and back.



* test(emb-1746): assert wrapper count, drop height assertion

The host-style-height assertion couldn't discriminate the fix from the
bug. The wrapper has `min-height: 100%` from its CSS module, so when
the mount container is taller than the host-provided `style={height: 400}`,
the computed height resolves to the container's height (800px in the
component-test viewport) in both pre-fix and post-fix scenarios.

The real structural invariant is the wrapper count: pre-fix, navigation
produced a second nested `SdkDashboardStyledWrapper`; post-fix, exactly
one. Assert `have.length === 1` at every step instead.



* Simplify comment

* Make test actually testing scrolling and assert the filters

* Address review

---------

Co-authored-by: Mahatthana (Kelvin) Nomsawadi <me@bboykelvin.dev>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix impersonated model caching  (metabase#74303)

* fix impersonated model caching

* fix kondo

Co-authored-by: Riley Thompson <riley@metabase.com>
…tabase#74453)

Skip H2 databases during serdes extract (GHY-3633) (metabase#74429)

* Skip H2 databases during serdes extract

The import path rejects H2 databases via assert-not-h2!, but the export
path included them, so a basic fresh-instance round trip failed
immediately on Sample Database / Internal Metabase Database. Filter
H2 out of extract-query "Database" so the export/import contract is
consistent.

Fixes GHY-3633.

* Gate H2-extract filter on dynamic var so H2-round-trip tests still work

The previous commit unconditionally filtered H2 from the Database
extract-query, which broke the v2.e2e / v2.load / v2.round-trip /
api-test suites that exercise H2 throughout (they already rebind
assert-not-h2! to allow it). Move the filter behind a dynamic var
*include-h2-in-extract?* that those fixtures can flip back on, and
give the API export test an explicit non-H2 database so it still
verifies the data-model export contains a Database entry.

* Drop H2-extract escape hatch from round-trip-test, force :postgres in a few setups

Round-trip-test now reads its baseline directly without bypassing
assert-not-h2! or flipping *include-h2-in-extract?* — the baseline DB
is :postgres, so neither hatch is needed. A handful of e2e/load
fixture databases that were defaulting to :h2 now declare :postgres
explicitly. The e2e and load test fixtures still need both redefs
because ~100 Card with-temps lean on the (data/id) test-data default
(H2); shrinking that surface is for another pass.

Co-authored-by: Eric Normand <ericwnormand@gmail.com>
security-center prometheus metrics (metabase#73730)

* add prometheus metrics for security center

* fix: last-synced-at should only be set on successful sync

* add test for last-synced-at behavior on sync failure

Co-authored-by: Nicola Mometto <nicola.mometto@metabase.com>
…nc" (metabase#74479)

Allow serialization to import transform tables after sync (metabase#74474)

fix: relax data source override none->transform during deser

Co-authored-by: Nicola Mometto <nicola.mometto@metabase.com>
…in email renderer" (metabase#74489)

Fix conditional formatting colors for BigDecimal cells in email renderer (metabase#74475)

Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
…e#74481)

* Add transform as a serdes dependency of table (metabase#74473)

* fix: transform is a dep of table for serdes

* feat: add deser tests for transform tables

* Fix backport tests for transform-created table serdes (61)

Transform's before-insert on 61 calls upsert-target-table! (pre-metabase#73741
behavior), so the metabase_table row already exists by the time the
tests try to create it. Adopt the existing row via select-one-pk +
update!, mirroring what sync-target! does after a real transform run.

---------

Co-authored-by: Nicola Mometto <nicola.mometto@metabase.com>
Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
docs: transform addons (metabase#74379)

* first draft

* almost there

* transformssss

* Optimized 3 images, saved 147.8 KB (46.2% smaller)

* links

* more links

* Apply suggestions from code review



* Update docs/data-studio/transforms/jobs-and-runs.md



* feedback

* make the top more of a tldr

---------

Co-authored-by: Alex Yarosh <alexandra@metabase.com>
Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
…etabase#74503)

Add no writable databases state to transforms pages (metabase#72880)

* Add no writable databases state to transforms pages

* add manage database permissions check

* only show no writable screen on DB write check, not permissions

* oss e2e adjustment

* handle error and loading states

Co-authored-by: Nick Fitzpatrick <nick@metabase.com>
…base#74513)

docs: add docs for scatterplots and bubble chart (metabase#74298)

* docs: add docs for scatterplots and bubble chart

* Add images and examples for additional data shapes

* Additional edits from Alex

Co-authored-by: Michael Eby <38187276+michaeleby1@users.noreply.github.com>
…74510)

GRO-411 Corrects transform inspector upsell (metabase#74227)

Co-authored-by: Brad Anderson <brad@metabase.com>
…sing source columns" (metabase#74518)

Guard v60 dependency_status data migrations against missing source columns (metabase#74463)

* Guard v60 dependency_status data migrations against missing source columns

Changesets v60.2026-04-02T00:00:03 through T00:00:10 copy
dependency_analysis_version from eight entity tables into the new
dependency_status table; later changesets in the same file drop those
source columns. If an instance ever enters a partial-migration state
where T00:00:23 (DROP COLUMN segment.dependency_analysis_version) has
run but T00:00:09 (the corresponding INSERT) has not, init crashes on
the next upgrade with "column dependency_analysis_version does not
exist".

Add a columnExists precondition with onFail: MARK_RAN to each of the
eight INSERT changesets, mirroring the guard already present on the
v58 changeset that adds the column. validCheckSum: ANY is included as
a defensive measure following the precedent in 059_update_migrations.

Adds a schema migration test that reproduces the partial-migration
state on H2 and Postgres and asserts T00:00:09 is recorded as MARK_RAN.

* Fix test changelog table-name lookup for MySQL/MariaDB

The hard-coded "databasechangelog" identifier in the SELECT works on
H2 and Postgres but not MySQL/MariaDB, where Liquibase creates the
table as uppercase DATABASECHANGELOG. Use the cross-DB
liquibase/changelog-by-id helper instead.

Co-authored-by: Eric Normand <ericwnormand@gmail.com>
…se#74548)

docs: fix minor typo in SDK Bundle description (metabase#74541)

Fix minor typo in SDK Bundle description

Co-authored-by: Alex Yarosh <alexandra@metabase.com>
…etabase#74543)

[AI Analytics] Date filters respect retention period (metabase#74441)

* min date

* max date

* util clean up

* clean up

* improved code comment

* more unit tests

Co-authored-by: Sloan Sparger <sloansparger@users.noreply.github.com>
…se#74372)

Strip redundant card fields from serdes output (metabase#72055)

* Strip redundant card fields from serdes output

Cards export table_id, database_id, query_type, and source_card_id, but
all four are derivable from dataset_query by populate-query-fields on
insert. Segments and Measures export table_id, derivable from their
definition via lib/primary-source-table-id on insert.

Strip these from export so the YAML is minimal and authoritative — the
query is the source of truth, not redundant denormalized fields.

database_id is kept when dataset_query is empty (no :database key to
derive from). Segment/Measure storage-path updated to derive the table
path from the definition when table_id is absent.

for reference:
```clojure
(mu/defn populate-query-fields
  "Lift `database_id`, `table_id`, `query_type`, and `source_card_id` fields
  from query definition when inserting/updating a Card."
  [{query :dataset_query, :as card} :- ::queries.schema/card]
  (merge
   card
   (when (and (seq query) (map? query))
     (merge
      ;; This used to be conditional on not nilling source-card-id, changed due to metabase#68080.
      {:source_card_id (source-card-id query)}
      (when-let [{:keys [database-id table-id]} (query/query->database-and-table-ids query)]
        ;; TODO -- not sure `query_type` is actually used for anything important anyway
        (let [query-type (if (query/query-is-native? query)
                           :native
                           :query)]
          (merge
           {:query_type (keyword query-type)}
           (when database-id
             {:database_id database-id})
           (when table-id
             {:table_id table-id}))))))))
```

Closes GHY-3325

* unimport model/Table

* re-export with fewer columns

* fix last tests

* remove last reference

* grab table_id from query using a serdes fn

optionally write out table_id

table_id is only skipped when it is derivable from the query. this
should in general happen unless the query is broken or empty `{}`.

* handle measures/metrics that lack a definition

tests caught these but real stuff could totally have this

FAIL in metabase-enterprise.remote-sync.impl-test/export!-excludes-archived-measures-test (impl_test.clj:1025)
export! excludes archived measures from export (via skip-archived flag)
(with-model-cleanup "RemoteSyncObject" "Measure")

Setting :remote-sync-type = :read-write

with temporary :model/Database

with temporary :model/Collection

with temporary :model/Table

with temporary :model/Measure

with temporary :model/Measure

expected: :success
  actual: :error

FAIL in metabase-enterprise.remote-sync.impl-test/export!-excludes-archived-measures-test (impl_test.clj:1028)
export! excludes archived measures from export (via skip-archived flag)
(with-model-cleanup "RemoteSyncObject" "Measure")

Setting :remote-sync-type = :read-write

with temporary :model/Database

with temporary :model/Collection

with temporary :model/Table

with temporary :model/Measure

with temporary :model/Measure

Active measure should be exported. Keys: ("collections/M-Q4pcV0qkiyJ0kiSWECl_some_collection/M-Q4pcV0qkiyJ0kiSWECl_some_collection.yaml" "collections/M-Q4pcV0qkiyJ0kiSWECl_some_collection/cards/f1C68pznmrpN1F5xFDj6d_some_question.yaml" "collections/M-Q4pcV0qkiyJ0kiSWECl_some_collection/dashboards/Q_jD-f-9clKLFZ2TfUG2h_shared_dashboard.yaml")
expected: (some
           (fn* [p1__314668#] (str/includes? p1__314668# "active_measure"))
           file-keys)
  actual: (not
           (some
            #<Fn@2c1faff4 metabase_enterprise.remote_sync.impl_test/fn__314670[fn/fn/fn/fn/fn/fn/fn/fn/fn]>
            ("collections/M-Q4pcV0qkiyJ0kiSWECl_some_collection/M-Q4pcV0qkiyJ0kiSWECl_some_collection.yaml"
             "collections/M-Q4pcV0qkiyJ0kiSWECl_some_collection/cards/f1C68pznmrpN1F5xFDj6d_some_question.yaml"
             "collections/M-Q4pcV0qkiyJ0kiSWECl_some_collection/dashboards/Q_jD-f-9clKLFZ2TfUG2h_shared_dashboard.yaml")))

ERROR in metabase-enterprise.serialization.v2.extract-test/collection-export-includes-fields-and-segments-test (serialization.clj:536)
Uncaught exception, not in assertion.

clojure.lang.ExceptionInfo: Error extracting Segment 1
                    cause: "Error extracting Segment 1"
                entity_id: "FTAODs9FCLwPAsC-_7Nb3"
                       id: 1
                    model: "Segment"
                    table: :segment
    toucan2/context-trace: [["execute SQL with class org.h2.jdbc.JdbcConnection"
                             #:toucan2.jdbc.query{:sql-args ["SELECT * FROM \"SEGMENT\" WHERE \"ID\" IN (?)" 1]}]
                            ["resolve connection" #:toucan2.connection{:connectable org.h2.jdbc.JdbcConnection}]
                            ["resolve connection" #:toucan2.connection{:connectable nil}]
                            #:toucan2.pipeline{:rf #function[clojure.core/map/fn--5952/fn--5953]}
                            ["with compiled query" #:toucan2.pipeline{:compiled-query ["SELECT * FROM \"SEGMENT\" WHERE \"ID\" IN (?)" 1]}]
                            ["with built query" #:toucan2.pipeline{:built-query {:select [:*], :from [[:segment]], :where [:in :id [1]]}}]
                            ["with resolved query" #:toucan2.pipeline{:resolved-query {:where [:in :id [1]]}}]
                            ["with parsed args"
                             #:toucan2.pipeline{:query-type :toucan.query-type/select.instances,
                                                :parsed-args {:queryable {:where [:in :id [1]]}}}]
                            ["with model" #:toucan2.pipeline{:model :model/Segment}]
                            ["with unparsed args"
                             #:toucan2.pipeline{:query-type :toucan.query-type/select.instances,
                                                :unparsed-args (:model/Segment {:where [:in :id [1]]})}]
                            ...]
clojure.lang.ExceptionInfo: Error extracting Segment 1
        error: {:schema [:ref :metabase.lib.schema/query],
                :value {},
                :errors
                ({:path [0 0 0 :lib/type],
                  :in [:lib/type],
                  :schema

* Export field elision is conditional on derivability

* Revert conditional elision for table-id and source-card-id

* t2 hook is source of truth

* m.

* Use resolve

* Use mu/disable-enforcement, revert some previous changes

---------

Co-authored-by: dpsutton <dan@dpsutton.com>
Co-authored-by: Timothy Dean <timothy@timothydean.dev>
…fields" (metabase#74554)

[AI Analytics] Make conversation list sortable by more fields (metabase#74380)

* impl

* pr feedback

Co-authored-by: Sloan Sparger <sloansparger@users.noreply.github.com>
…abase#74558)

Reproduce and fix deadlock issue with remote sync (metabase#74544)

`load-metabase!` wrapped the entire import in a single transaction, holding row-level locks for the duration. On a live instance, concurrent writers (e.g., view-count batching, result-metadata updates) could deadlock with the import.

I split the transaction into per-entity transactions. Each `serdes/load-one!` call gets one. If an error is thrown, I attempt to check for known transient errors and retry with exponential backoff.

Co-authored-by: Timothy S. Dean <timothy.dean@metabase.com>
…er" (metabase#73828)

deps: exclude azure-core-http-netty from snowflake driver (metabase#73824)

* deps: exclude azure-core-http-netty from snowflake driver

* ci: trigger fresh run with break-quarantine-snowflake label

Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
…74579)

fix: add metabase_version to serdes extract (metabase#74476)

* fix: add metabase_version to serdes extract

* feat: silence version mismatch when version is not present

* tests

* the existing test that would've caught it was broken!

* ignore in existing tests

Co-authored-by: Nicola Mometto <nicola.mometto@metabase.com>
… for integer" (metabase#74584)

Uxw 3359 visualizer breaks internal dimension remapping for integer (metabase#74446)

* Fix visualizer dropping internal dimension remapping, make cards combineable

* Fix removing and re-adding integer-category dimensions in the visualizer

* PR Feedback, adjust helper function and variable names

Co-authored-by: Nick Fitzpatrick <nick@metabase.com>
* Add backport resolution script

* Backport

* Remove backport.sh

* Fix unit tests

---------

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: losrebellos <ben@benoitvinay.com>
github-automation-metabase and others added 28 commits May 22, 2026 09:47
…abase#74630)

Fix /api/frontend-errors 404 in embedded contexts (metabase#74623)

Co-authored-by: Sébastien <sebastien@metabase.com>
Co-authored-by: Timofei <timofei@metabase.com>
Co-authored-by: Phoomparin Mano <phoomparin@gmail.com>
Co-authored-by: Chris Truter <chris@metabase.com>
…etabase#74626)

Use transform timeout for queries inside transforms (metabase#72522)

* Use transform timeout for queries inside transforms

Previously, queries executed within a transform were subject to the lower
of MB_DB_QUERY_TIMEOUT_MINUTES (default 20 min) and MB_TRANSFORM_TIMEOUT
(default 4 h). This made MB_TRANSFORM_TIMEOUT effectively unusable for its
stated purpose: long-running transforms always hit the query timeout first.

Bind *query-timeout-ms* to the transform timeout inside
run-cancelable-transform! so transform queries respect MB_TRANSFORM_TIMEOUT,
while regular Metabase queries keep using MB_DB_QUERY_TIMEOUT_MINUTES.

Fixes GDGT-2173.

* Enforce query timeouts per-statement, not via pool leak-detector

The c3p0 unreturnedConnectionTimeout was doing double duty: leak
detection AND query-timeout enforcement. That pool-wide conflation
meant transforms running longer than db-query-timeout always got their
connection destroyed first, defeating the transform-timeout rebinding
introduced earlier in this branch.

This commit:
- Calls Statement.setQueryTimeout uniformly in prepared-statement*/
  statement* so every SQL-JDBC query carries its own server-side
  timeout derived from *query-timeout-ms*. Applied after the multimethod
  returns, so driver overrides that replace the base method also get it.
- Raises the pool's unreturnedConnectionTimeout default to
  max(db-query-timeout, transform-timeout) so the safety net does not
  undercut a legitimate long transform; per-statement timeouts remain
  the real query killer.
- Also rebinds *network-timeout-ms* inside run-cancelable-transform! so
  a single slow socket read does not kill a long transform earlier than
  its own deadline.
- Replaces the prior tautological test with two real ones: the pool
  setting default correctly maxes query-timeout and transform-timeout,
  and a real H2 statement created through statement-or-prepared-statement
  observes the rebound timeout via .getQueryTimeout.

GDGT-2173

* Revert "Enforce query timeouts per-statement, not via pool leak-detector"

This reverts commit 5e01c82.

* Reapply "Enforce query timeouts per-statement, not via pool leak-detector"

This reverts commit a9f2942.

* Simplify: strip ticket refs from source, hoist transform-timeout let

* Stronger tests: proxy-based unit test + multi-driver sweep for setQueryTimeout

* Route transform-timeout into driver pool default via registry, not cross-module lookup

Replaces the requiring-resolve from driver/settings.clj into transforms with a
register-long-running-timeout-provider! registry that transforms self-populates.
Keeps the dependency direction correct (transforms -> driver) and silences the
:metabase/modules kondo check. Also drops ^:parallel from the set-statement-query-timeout!
unit test, which tripped the destructive-fn-in-parallel-test linter.

* Route transforms through a dedicated :transform connection pool

Replaces the cross-feature `register-long-running-timeout-provider!`
registry with a new `:transform` value on the existing `*connection-type*`
enum, so transforms reuse the pool-cache key `[db-id, connection-type]`
mechanism that already isolates `:write-data`. The `:default` pool's
`unreturnedConnectionTimeout` is now unchanged from pre-PR — only the
`:transform` pool sees the longer leak-detector window — addressing
reviewer concerns about widening the safety net for non-transform queries.

* resolve-connection-type: take connection-type as arg, drop dynamic-var read

Pure-function refactor — caller passes *connection-type* (or any value)
explicitly. Easier to test, no implicit dependency on the dynamic binding.

* :transform also merges write-data-details; restore catch Throwable

Transforms route through `with-write-connection` (query_impl.clj:30), so
they need write-data credentials when configured. My earlier refactor's
`with-transform-connection` rebind dropped those credentials by treating
`:transform` as a separate write-context from `:write-data`. Fixed by
having `effective-details` merge `:write-data-details` when *connection-type*
is either `:write-data` or `:transform` — the pool key still differs so
pool properties (unreturnedConnectionTimeout) can be tuned per workload.

Updated `transform-creates-write-pool-test` -> `transform-creates-transform-pool-test`
to assert the new `[db-id :transform]` cache key.

Restored `catch Throwable` in `set-statement-query-timeout!` to match the
original PR — Spark/Hive may throw `AbstractMethodError` (an `Error`,
not `Exception`); the previous PR's `catch Throwable` swallowed it.

* Gate Statement.setQueryTimeout behind :jdbc/set-query-timeout feature flag

SparkSQL's Hive JDBC driver appears to handle Statement.setQueryTimeout
by closing the Thrift transport on the server side; subsequent statement
close() then throws "Failed to close statement" with TTransportException:
Socket is closed by peer. The catch in set-statement-query-timeout!
silences the setQueryTimeout call itself, but the session is already
poisoned and any explicit-joins test errors on .close().

Adds :jdbc/set-query-timeout feature flag (defaults true on :sql-jdbc,
overridden false on :sparksql). set-statement-query-timeout! takes the
driver as an arg and skips the call entirely when the feature is off.

* Skip getQueryTimeout assertion for drivers that opt out of setQueryTimeout

Spark/Hive opts out via :jdbc/set-query-timeout=false, so getQueryTimeout()
returns the JDBC default (0), not the bound *query-timeout-ms*. Branch
the assertion: drivers that support setQueryTimeout still round-trip;
opt-outs only assert a Statement was created.

* Clarify :transform connection-type docstrings per review

The with-transform-connection docstring claimed :transform shares primary
:details with :default, but effective-details merges :write-data-details for
:transform just like :write-data. Reconcile *connection-type*,
with-transform-connection, and resolve-connection-type docstrings to that truth:
:transform resolves details like :write-data but routes through a separate pool
key so c3p0 can carry transform-tolerant properties.

Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
)

[perf] Optimize fingerprinters/mode-stats (metabase#74327)

Co-authored-by: Oleksandr Yakushev <alex@bytopia.org>
…cus" (metabase#74540)

[Metabot] Fix AI settings inputs flickering + losing focus (metabase#74527)

* fix model dropdown flicker

* fix provider dropdown flicker

* fix api key input flicker

* fix connect/disconnect/done button flicker

* fix terms checkbox flicker

* drop unused isLoading from MetabotSetupContext

* consolidate BOT-1429 regression tests into one

Co-authored-by: Sloan Sparger <sloansparger@users.noreply.github.com>
Allow custom source for ID params (metabase#74650)

Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com>
…ENTION_DAYS" (metabase#74659)

BOT-1495 Trim all metabot tables to MB_AI_USAGE_MAX_RETENTION_DAYS (metabase#74035)

* BOT-1495 Trim all metabot append-only tables to MB_AI_USAGE_MAX_RETENTION_DAYS

As of BOT-1409 we now trim the ai_usage_log table to prevent unbounded growth. The same arguments for trimming the
ai_usage_log table apply to other metabot append-only tables, like metabot_conversation, metabot_message,
metabot_feedback, and metabot_source_feedback.

- Update the fk on `metabot_message` pointing to `metabot_conversation.id` to `ON DELETE CASCADE`
- Add a trimmer task for `metabot_conversation` to also respect `MB_AI_USAGE_MAX_RETENTION_DAYS`.

Note that if we don't do this, older conversations will not have any source or profile_id associated, since these come
from the ai_usage_log table. This is not the only cause, but one possible cause of issues like BOT-1415

See also

* metabase#73400
* metabase#73518

* Shorten the gap between the ai_usage_log and metabot_conversation trimmer tasks

Co-authored-by: appleby <86076+appleby@users.noreply.github.com>
…etabase#74667)

Enable SMTP debug logs with mail.debug JVM property (metabase#67519)

drewr/postal uses Jakarta Mail, and supports a debug option when sending
messages. However, it requires callers of the library to manually pass the
debug option, and doesn't look at any JVM options.

This change makes it so that setting the mail.debug JVM property to true
enables debug logging for all outgoing SMTP messages. No credentials are
logged.

Co-authored-by: Rodrigo López Dato <rodrigo@metabase.com>
Pin `jest-canvas-mock` (metabase#74509)

Co-authored-by: Nemanja Glumac <31325167+nemanjaglumac@users.noreply.github.com>
…t schema" (metabase#74721)

Fix Snowflake python transform e2e test: explicit target schema (metabase#74716)

Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
Handle query cache update race (metabase#74587)

Closes metabase#73770

Co-authored-by: metamben <103100869+metamben@users.noreply.github.com>
…#74696)

handle duplicate copies of gson on classpath (metabase#74685)

* handle duplicate copies of gson on classpath

fixes metabase#73736

The relevant error message for us:

```
java.lang.NoSuchMethodError:
  'com.google.gson.stream.JsonWriter com.google.gson.stream.JsonWriter.value(float)'
  com.google.api.client.json.gson.GsonGenerator.writeNumber(GsonGenerator.java:105)
  com.google.cloud.bigquery.BigQueryRetryAlgorithm.getErrorDescFromResponse(BigQueryRetryAlgorithm.java:227)
  com.google.cloud.bigquery.BigQueryRetryAlgorithm.shouldRetryBasedOnBigQueryRetryConfig(BigQueryRetryAlgorithm.java:115)
  com.google.cloud.bigquery.BigQueryRetryHelper.run(...:108)
  driver.bigquery_cloud_sdk$execute_bigquery(bigquery_cloud_sdk.clj:751)
```

```clojure
user=> (enumeration-seq (.. (Thread/currentThread) getContextClassLoader (getResources "com/google/gson/stream/JsonWriter.class")))
(#object[java.net.URL
         "0x3bc8ea4f"
         "jar:file:/Users/dan/.m2/repository/com/google/code/gson/gson/2.12.1/gson-2.12.1.jar!/com/google/gson/stream/JsonWriter.class"]
 #object[java.net.URL
         "0x50073d1c"
         "jar:file:/Users/dan/.m2/repository/com/vertica/jdbc/vertica-jdbc/23.4.0-0/vertica-jdbc-23.4.0-0.jar!/com/google/gson/stream/JsonWriter.class"])
```

- Vertica bundles: gson 2.8.9 (vendored, unshaded)
- BigQuery demands: gson 2.12.1 (via com.google.code.gson/gson in deps tree — needs JsonWriter.value(float) which was added in 2.9.0)

contents of vertica jar:
 71 com/vertica/io
 70 com/google/gson/internal/bind
 61 com/vertica/utilities/conversion

188 gson class files bundled inside vertica-jdbc, vs ~600 Vertica
classes. They just vendored the entire gson library unshaded into their
fat JAR. Only gson — nothing else third-party.

So mystery solved. We had two copies of gson and vertica could slip
through. Our fix is to ensure that we only accept (or overwrite) when it
comes from a gson lib

```clojure
(def ^:private gson-conflict-handler
  "vertica-jdbc (and potentially other fat JARs) bundle their own copy of gson classes.
   When these overwrite the correct version from com.google.code.gson/gson, BigQuery's
   error-handling path crashes with NoSuchMethodError on JsonWriter.value(float),
   introduced in gson 2.9.0. This handler ensures the pinned gson version always wins
   regardless of JAR processing order. See metabase#73736."
  {"com/google/gson/.*"
   (fn [{:keys [lib path in]}]
     (if (= lib 'com.google.code.gson/gson)
       {:write {path {:stream in}}}
       nil))})
```

* add activation conflict handler as well

```clojure
uberjar-test=> (clojure.test/run-tests)

Testing build.uberjar-test
Clean
  Delete /Users/dan/projects/work/metabase/target/classes
  Delete /Users/dan/projects/work/metabase/target/uberjar/metabase.jar

FAIL in (class-file-conflicts-test) (uberjar_test.clj:55)
No unexpected class file conflicts in the EE uberjar
Unexpected class file conflicts in packages: #{"javax/activation"}
If benign, add to known-conflicting-prefixes with a comment. If dangerous, add a conflict handler in build.uberjar.
expected: (empty? unexpected)
  actual: (not (empty? #{"javax/activation"}))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
{:test 1, :pass 0, :fail 1, :error 0, :type :summary}
```

Here's the output when i removed the `activation-conflict-handler` from
the uberjar-er. It hits conflicts on these files.

It doesn't know where the conflicts come from. The api is aware of the
lib trying to write and there's already a conflict. So the order isn't
stable and we must match on the files being written, not the lib
writing. Because lib A might write first so lib B tries to write the
same class we think it's conflict on B. But then B is written first and
now it appears a conflict in A.

also an exclusion in databricks so it doesn't bring in another logger
and we will no longer get this:

```
❯ clj -J"$(llm-repl 6006)" -M:"$ALIASES":build:build-dev:llm-repl
Downloading: vlaaad/reveal/maven-metadata.xml from clojars
WARNING: Specified aliases are undeclared and are not being used: [:performance]
SLF4J(W): Class path contains multiple SLF4J providers.
SLF4J(W): Found provider [org.apache.logging.slf4j.SLF4JServiceProvider@122ea8dc]
SLF4J(W): Found provider [org.slf4j.jul.JULServiceProvider@6cb417fc]
SLF4J(W): See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J(I): Actual provider is of type [org.apache.logging.slf4j.SLF4JServiceProvider@122ea8dc]
```

* fixing it up from comments

Co-authored-by: dpsutton <dan@dpsutton.com>
Fix remapping with custom values (metabase#74698)

* fix remapping with custom values

* fix

* fix unit test

Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com>
…mails is on" (metabase#74745)

security center: only include mb email if send to all emails is on (metabase#74334)

only include mb email if send to all emails is on

Co-authored-by: Nicola Mometto <nicola.mometto@metabase.com>
…base-browser" (metabase#74757)

Show collection picker when saving new question in metabase-browser (metabase#74570)

Co-authored-by: Sébastien <sebastien@metabase.com>
…base#74761)

feat: ui to display data complexity color coding (metabase#74363)

Closes [BOT-1519: UI to display data complexity score and color coding](https://linear.app/metabase/issue/BOT-1519/ui-to-display-data-complexity-score-and-color-coding)

### Description

Adds UI to display data complexity score and color coding.

### How to verify

1. Go to AI usage analytics page.
2. Go to data complexity scores.

### Demo

https://www.loom.com/share/81901a478d1a45a3839d7dd42f3e4ae7?from_recorder=1&focus_title=1

### Checklist

- [x] Tests have been added/updated to cover changes in this PR
- [x] If adding new Loki tests: they pass [stress testing](https://github.com/metabase/metabase/actions/workflows/loki-stress-test-flake-fix.yml)

Co-authored-by: Maksym Yakubych <undrfined@gmail.com>
…ipients" (metabase#74762)

Security center: send test notifications to unsaved recipients (metabase#74339)

* feat: pass explicit config

* api: build config

* update FE

* Update enterprise/frontend/src/metabase-enterprise/security_center/hooks/use-notification-config.ts



* handle FE review comments

* extend test

---------

Co-authored-by: Nicola Mometto <nicola.mometto@metabase.com>
Co-authored-by: Kamil Mielnik <kamil@kamilmielnik.com>
Fix search values remap (metabase#74770)

* fix custom values with labels

* fix

Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com>
Allow Admins to purchase Metabot AI (metabase#74602)

* Allow Admins to purchase Metabot AI

* update unit tests

Co-authored-by: Ryan Laurie <30528226+iethree@users.noreply.github.com>
docs - bitbucket remote sync (metabase#74772)

bitbucket tokens

Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
…4672)

Prevent snowflake tests from deleting old data and failing build.

Manual backport of metabase#74663

Co-authored-by: Phil Hagelberg <phil@hagelb.org>
)

[Metabot] Show message feedback UI in OSS (metabase#74555)

* show feedback buttons in self-hosted instances

* clean up ishosted flag in setup fn

* clean up

Co-authored-by: Sloan Sparger <sloansparger@users.noreply.github.com>
…ssages" (metabase#74793)

Co-authored-by: Chris Truter <crisptrutski@users.noreply.github.com>
fix for the same gap addressed in PR metabase#74356
… range stats" (metabase#74708)

Fix NullPointerException in render-minibar with missing range stats (metabase#74572)

* Fix NullPointerException in render-minibar with missing range stats

render-minibar assumed the column fingerprint always supplied min and max
values. When a minibar-enabled column lacks :type/Number fingerprint stats
(e.g. an unsynced or computed column), min and max are nil and (< min 0)
throws a NullPointerException that crashes the entire Slack/email notification
delivery silently. Guard against nil min/max and fall back to rendering the
plain formatted value, consistent with the existing non-numeric fallback.

* Isolate per-card render failures in dashboard subscriptions

A single card that fails to render (e.g. the minibar NPE in metabase#74007, or any
future render error) could take down an entire dashboard subscription: the
per-card try/catch in render-pulse-card-body swaps in a :render-error
placeholder, but render methods build the body as lazy hiccup, so an
exception thrown while building it escapes that catch and only fires later
when the channel realizes the hiccup (email: hiccup/html; Slack: PNG
rendering) — outside any per-card boundary, aborting the whole notification.

Add explicit per-part isolation at the assembly boundary, where each part is
already rendered and realized one at a time:
- email :notification/dashboard reduce wraps each part's render+html
- slack :notification/dashboard mapcat wraps each part->sections!

On failure the part degrades to the existing error placeholder (new
channel.render/error-rendered-part for email; an equivalent section block for
Slack) and the remaining cards still deliver. This catches any part failure,
lazy or not, and leaves the happy path unchanged.

* Simplify min/max nil check in render-minibar

Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
@trevorgowing trevorgowing merged commit b2cd13a into master Jun 26, 2026
@trevorgowing trevorgowing deleted the sync/upstream-2026-06-08 branch June 26, 2026 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants